﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace MetMax.Importer
{
    public abstract class Base : BackgroundWorker
    {
        #region constructors
        protected Base()
            : base()
        {
            base.DoWork += new DoWorkEventHandler(Importer_Base_DoWork);
            base.WorkerReportsProgress = true;
            base.WorkerSupportsCancellation = true;
        }

        protected Base(DataTable dt, string[] filenames, Options opt)
            : this()
        {
            this.dt = dt;
            this.filenames = filenames;
            this.opt = opt;
        }

        protected Base(BackgroundWorkerArgument bgwa, Options opt)
            : this(bgwa.dt, bgwa.filenames, opt)
        {
        }
        #endregion constructors

        public DataTable dt { get; set; }
        public string[] filenames { get; set; }
        protected Options opt { get; set; }

        public abstract void Import(string filename, DataColumn currentfileColumn);

        void Importer_Base_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                dt.Columns[i].ColumnName = dt.Columns[i].ColumnName.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)[0];
            }

            int counter = 0;

            foreach (string filename in filenames)
            {
                if (CancellationPending)
                {
                    break;
                }

                try
                {
                    FileInfo fi = new FileInfo(filename);
                    if (dt.Columns.Contains(fi.Name))
                    {
                        dt.Columns.Remove(fi.Name);
                    }

                    DataColumn currentfileColumn = new DataColumn(fi.Name, typeof(BusinessObject.Base));
                    dt.Columns.Add(currentfileColumn);

                    Import(filename, currentfileColumn);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

                DataTable dtcopy;
                lock (dt)
                {
                    dtcopy = dt.Copy();
                }

                ReportProgress(++counter, new ProgressReportObject { filename = filename, dt = dtcopy });
                //Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, filename);
            }

            //compute the averaged ri if possible
            if (dt is MetMaxDataSet.resultDataTable)
            {
                DataColumn riColumn = (dt as MetMaxDataSet.resultDataTable).riColumn;
                foreach (DataRow dr in dt.Rows)
                {
                    dr[riColumn] = (from p in dr.ItemArray.OfType<BusinessObject.Base>() select p.ri.Value).Average();
                }
            }
        }

        protected static IEnumerable<BusinessObject.Base> LoadFile(string fileName)
        {
            using (StreamReader sr = File.OpenText(fileName))
            {
                int firstCharacter = 13;
                while ((firstCharacter != -1) && firstCharacter == 13)
                {
                    firstCharacter = sr.Peek();
                    if (firstCharacter == 13)
                    {
                        sr.ReadLine();
                    }
                }

                //search the appropiate column 'area' to extract the data from
                int currentColumnIndex = 0;
                SortedList<string, int> columns = new SortedList<string, int>();
                string[] split = sr.ReadLine().Split(new char[] { '\t' });

                List<string> ExpectedColumns = ExpectedFileColumns().ToList();
                ExpectedColumns.Sort();

                foreach (string s in split)
                {
                    if (ExpectedColumns.BinarySearch(s) >= 0)
                    {
                        columns.Add(s, currentColumnIndex); // we need this column
                    }
                    currentColumnIndex++;
                }

                if (columns.Count != ExpectedFileColumns().Count())
                {
                    StringBuilder sb_expectedColumns = new StringBuilder();
                    StringBuilder sb_foundColumns = new StringBuilder();

                    foreach (string column in ExpectedFileColumns())
                    {
                        if (columns.ContainsKey(column))
                        {
                            sb_foundColumns.AppendFormat("'{0}', ", column);
                        }
                        else
                        {
                            sb_expectedColumns.AppendFormat("'{0}', ", column);
                        }
                    }
                    if (sb_expectedColumns.Length > 0)//remove the last whitespace and commata
                    {
                        sb_expectedColumns.Length = sb_expectedColumns.Length - 2;
                    }
                    if (sb_foundColumns.Length > 0)//remove the last whitespace and commata
                    {
                        sb_foundColumns.Length = sb_foundColumns.Length - 2;
                    }

                    throw new NotSupportedException(string.Format(
                        "missing columns in datafile '{0}'.\r\n\r\nverified columns [{1}].\r\n\r\nstill missing columns [{2}].\r\n\r\nPlease review the file and try again!"
                        , fileName
                        , sb_foundColumns.ToString()
                        , sb_expectedColumns.ToString()));
                }

                BusinessObject.Base bo;
                foreach (string[] splittedLine in LoadSingleSplittedLineFromFile("\t", sr).AsParallel())
                {
                    bo = new BusinessObject.Base(MetMax.Properties.Settings.Default.myOptions)
                    {
                        name = splittedLine[columns["Name"]],
                        ri = ToFloat(splittedLine[columns["Retention Index"]]),
                        mass = splittedLine[columns["Quant Masses"]],
                        area = ToFloat(splittedLine[columns["Area"]]),
                        rt1 = ToFloat(splittedLine[columns["1st Dimension Time (s)"]]),
                        rt2 = ToFloat(splittedLine[columns["2nd Dimension Time (s)"]]),
                        spectrum = new udtMassSpectra(splittedLine[columns["Spectra"]]),
                        Concentration = ToFloat(splittedLine[columns["Concentration"]]),
                        Conc_Units = splittedLine[columns["Conc. Units"]],
                        Match = ToInt(splittedLine[columns["Match"]]),
                        Quant_SN = ToFloat(splittedLine[columns["Quant S/N"]]),
                        Reverse = ToInt(splittedLine[columns["Reverse"]]),
                        Type = splittedLine[columns["Type"]]
                    };
                    yield return bo;
                }
                sr.Close();
            }
        }

        private static IEnumerable<String[]> LoadSingleSplittedLineFromFile(String delimiter, StreamReader reader)
        {
            string line;

            while ((line = reader.ReadLine()) != null)
            {
                yield return line.Split(delimiter.ToCharArray());
            }
            //while (reader.Peek() != -1)
            //{
            //    yield return reader.ReadLine().Split(delimiter.ToCharArray());
            //}
            yield break;
        }

        internal static float? ToFloat(string s)
        {
            float ret;
            return float.TryParse(Regex.Replace(s, "\"", string.Empty), NumberStyles.AllowDecimalPoint | NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out ret) ? (float?)ret : null;
        }

        private static int? ToInt(string s)
        {
            int ret;
            return int.TryParse(Regex.Replace(s, "\"", string.Empty), NumberStyles.AllowDecimalPoint | NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out ret) ? (int?)ret : null;
        }

        private static IEnumerable<string> ExpectedFileColumns()
        {
            yield return "Name";
            yield return "Retention Index";
            yield return "Quant Masses";
            yield return "Area";
            yield return "1st Dimension Time (s)";
            yield return "2nd Dimension Time (s)";
            yield return "Spectra";

            yield return "Concentration";
            yield return "Conc. Units";
            yield return "Match";
            yield return "Quant S/N";
            yield return "Reverse";
            yield return "Type";

            yield break;
        }

        #region Fittings
        protected static Fitting.Polynomial getPolynomialFittingDegree3Rt1ToAlkaneIndex(IEnumerable<BusinessObject.Base> input)
        {
            Fitting.Polynomial res = new Fitting.Polynomial(3);

            var e = from p in input
                    let m = p.RiMarkerRegExMatch
                    where m.Groups["alkane"].Success
                    select new { rt1 = p.rt1.Value, alkaneindex = int.Parse(m.Groups["alkane"].Value) * 100 };

            foreach (var item in e)
            {
                res.Add(item.rt1, item.alkaneindex);
            }

            return res;
        }

        protected static Fitting.Spline getRelativeAlkanRetentionTime(IEnumerable<BusinessObject.Base> input)
        {
            var e = from p in input
                    let m = p.RiMarkerRegExMatch
                    where m.Groups["alkane"].Success
                    select new { rt1 = p.rt1.Value, alkaneindex = p.rt2wrapped };

            return new Fitting.Spline(e.ToDictionary(p => (double)p.rt1, p => (double)p.alkaneindex));
        }

        protected static Fitting.ObservationPointDictionary getRi1System(IEnumerable<BusinessObject.Base> input)
        {
            var e = from p in input
                    let m = p.RiMarkerRegExMatch
                    where m.Groups["alkane"].Success
                    orderby p.rt1.Value ascending
                    //select new { rt1 = p.rt1.Value, alkaneindex = p.riAlkan.Value }
                    select p;
            return new Fitting.ObservationPointDictionary(e.ToDictionary(p => (double)p.rt1.Value, p => (double)p.riAlkan.Value));
        }

        protected static Fitting.ObservationPointDictionary getRi2System(IEnumerable<BusinessObject.Base> input)
        {
            try
            {
                var e = from p in input
                        let m = p.RiMarkerRegExMatch
                        where m.Groups["alkane"].Success
                        orderby p.rt2wrapped.Value ascending
                        //select  new { rt2 = p.rt2wrapped.Value, alkaneindex = p.riAlkan.Value };
                        select p;
                return new Fitting.ObservationPointDictionary(
                    e.Distinct(new BusinessObject.BusinessObject_rt2wrapped_EqualityComparer())
                    .ToDictionary(p => (double)p.rt2wrapped.Value, p => (double)p.riAlkan.Value));
            }
            catch (Exception)
            {
                return null;
            }
        }
        #endregion Fittings
    }
}
